package com.titanrise.crm.common.utils;

import java.util.function.Function;
import java.util.function.Supplier;

public abstract class Either<L, R> {
    private Either() {
    }

    public static <L, R> Either<L, R> left(L value) {
        return new Left<>(value);
    }

    public static <L, R> Either<L, R> right(R value) {
        return new Right<>(value);
    }

    public static <L, R> Function<Maybe<R>, Either<L, R>> fromMaybe(L error) {
        return (maybe) -> maybe.<Either<L, R>>map(Either::right).orElse(Either.left(error));
    }

    public static <L extends Exception, R> Either<L, R> capture(Supplier<R> supplier, Class<L> exceptionType) {
        try {
            return Either.right(supplier.get());
        } catch (Exception e) {
            if (e.getClass().equals(exceptionType))
                return Either.left((L) e);
            else
                throw e;
        }
    }

    public abstract boolean isLeft();

    public abstract boolean isRight();

    public abstract L getLeft();

    public abstract R getRight();

    public R getOrElse(Function<L, R> fn) {
        if (this.isRight())
            return this.getRight();
        else
            return fn.apply(this.getLeft());
    }

    public <X extends Throwable> R orElseThrow(Function<L, ? extends X> fn) throws X {
        if (this.isRight())
            return this.getRight();
        else
            throw fn.apply(this.getLeft());
    }

    public <R2> Either<L, R2> flatMap(Function<R, Either<L, R2>> fn) {
        if (this.isRight())
            return fn.apply(this.getRight());
        else
            return Either.left(this.getLeft());
    }

    public <R2> Either<L, R2> map(Function<R, R2> fn) {
        if (this.isRight())
            return Either.right(fn.apply(this.getRight()));
        else
            return Either.left(this.getLeft());
    }

    public <L2> Either<L2, R> mapLeft(Function<L, L2> fn) {
        if (this.isRight())
            return Either.right(this.getRight());
        else
            return Either.left(fn.apply(this.getLeft()));
    }

    private static class Left<L, R> extends Either<L, R> {
        private final L value;

        private Left(L value) {
            this.value = value;
        }

        @Override
        public boolean isLeft() {
            return true;
        }

        @Override
        public boolean isRight() {
            return false;
        }

        @Override
        public L getLeft() {
            return value;
        }

        @Override
        public R getRight() {
            throw new UnsupportedOperationException("Cannot get right value from Left");
        }
    }

    private static class Right<L, R> extends Either<L, R> {
        private final R value;

        private Right(R value) {
            this.value = value;
        }

        @Override
        public boolean isLeft() {
            return false;
        }

        @Override
        public boolean isRight() {
            return true;
        }

        @Override
        public L getLeft() {
            throw new UnsupportedOperationException("Cannot get left value from Right");
        }

        @Override
        public R getRight() {
            return value;
        }
    }
}
